package com.coincalf.framework.utils;

import com.ripple.crypto.ecdsa.IKeyPair;
import com.ripple.crypto.ecdsa.Seed;
import com.ripple.encodings.base58.B58;
import lombok.Data;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.copyOfRange;

/**
 * 助记词service
 *
 * @author czj
 * @date 2019/7/8 12:01 PM
 */
public class MnemonicService {

    private static final Logger log = LoggerFactory.getLogger(MnemonicService.class);

    private static final String XRP_DICTIONARY = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
    private static final String TEST_SEED = "snWHuzunpPaixpfxcFMirAz36esGx";

    public static WalletResponse getAddressByCoinName(String coinName) {
        List<String> words = MnemonicService.generateMnemonicWords();
        if (coinName.equalsIgnoreCase("BTC") || coinName.equalsIgnoreCase("USDT_OMNI")) {
            return MnemonicService.getBtcAddress(words, 0, 1);
        }
        if (coinName.equalsIgnoreCase("ETH") || coinName.equalsIgnoreCase("USDT_ERC20")) {
            return MnemonicService.getEthAddress(words, 0);
        }
        if (coinName.equalsIgnoreCase("TRX") || coinName.equalsIgnoreCase("USDT_TRC20")) {
            return MnemonicService.getTrxAddress(words, 0, 1);
        }
        if (coinName.equalsIgnoreCase("LTC")) {
            return MnemonicService.getLtcAddress(words, 0);
        }
        if (coinName.equalsIgnoreCase("EOS")) {
            return MnemonicService.getEosAddress(words, 0);
        }
        if (coinName.equalsIgnoreCase("XRP")) {
            return MnemonicService.getXrpAddress(words, 0);
        }
        throw new RuntimeException("暂不支持该币种");
    }

    /**
     * 生成12个助记词
     *
     * @return
     */
    public static List<String> generateMnemonicWords() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] initialEntropy = new byte[16];
        secureRandom.nextBytes(initialEntropy);
        try {
            return MnemonicCode.INSTANCE.toMnemonic(initialEntropy);
        } catch (MnemonicException.MnemonicLengthException e) {
            //长度异常，不会发生，除非人为设置错误initialEntropy的长度
            e.printStackTrace();
            throw new RuntimeException("生成助记词异常，初始化byte数组长度错误");
        }
    }

    /**
     * 根据助记词生成种子
     *
     * @param words
     * @return
     */
    public static byte[] getSeedByMnemonic(List<String> words) {
        return MnemonicCode.toSeed(words, "");
    }

    /*************************************BTC START*************************************/
    /**
     * 根据助记词生成BTC/USDT地址
     *
     * @param index       address_index
     * @param addressType 地址类型 1为普通地址(1开头)  2隔离见证地址(3开头)
     * @return 地址
     */
    public static WalletResponse getBtcAddress(List<String> words, Integer index, Integer addressType) {
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        index = index == null ? 0 : index;
        //隔离见证地址  "m/49'/0'/0'/0/" + 0;  已升级为bip49协议
        String path;
        if (addressType == 2) {
            path = "m/49'/0'/0'/0/" + index;
        } else {
            path = "m/44'/0'/0'/0/" + index;
        }
        NetworkParameters networkParameters = MainNetParams.get();
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, getSeedByMnemonic(words), "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey());
        String privateKeyAsWiF = ecKey.getPrivateKeyAsWiF(networkParameters);
        walletResponse.setPrivateKey(privateKeyAsWiF);
        String address;
        if (addressType == 1) {
            address = LegacyAddress.fromKey(MainNetParams.get(), ecKey).toBase58();
        } else if (addressType == 2) {
            String redeemScript = String.format("0014%s", Hex.toHexString(ecKey.getPubKeyHash()));
            address = LegacyAddress.fromScriptHash(MainNetParams.get(), Utils.sha256hash160(Hex.decode(redeemScript))).toBase58();
        } else {
            throw new RuntimeException("BTC地址类型错误");
        }
        walletResponse.setAddress(address);
        return walletResponse;
    }
    /*************************************BTC END*************************************/

    /*************************************BCH START*************************************/
    /**
     * 生成BCH地址 1开头
     *
     * @param words 助记词
     * @param index 地址index 默认为0
     * @return
     */
    public static WalletResponse getBchAddressByMnemonic(List<String> words, Integer index) {
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        index = index == null ? 0 : index;
        String path = "m/44'/145'/0'/0/" + index;
        NetworkParameters networkParameters = MainNetParams.get();
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, getSeedByMnemonic(words), "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey());
        String privateKeyAsWiF = ecKey.getPrivateKeyAsWiF(networkParameters);
        walletResponse.setPrivateKey(privateKeyAsWiF);
        //公钥生成地址
        String publicKeyHex = ecKey.getPublicKeyAsHex();
        byte[] sha256Bytes = Sha256Hash.hash(Hex.decode(publicKeyHex));
        //2.将第1步结果进行RIPEMD160哈希
        RIPEMD160Digest digest = new RIPEMD160Digest();
        digest.update(sha256Bytes, 0, sha256Bytes.length);
        byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
        digest.doFinal(ripemd160Bytes, 0);
        //3.将BCH地址版本号(00)加在第2步结果签名
        String result = "00" + Hex.toHexString(ripemd160Bytes);
        //4.将第3步结果进行双SHA256哈希
        byte[] firstHash = Sha256Hash.hash(Hex.decode(result));
        byte[] doubleHash = Sha256Hash.hash(firstHash);
        //5.取第4步结果的前4字节，并加在第3步结果后面
        String checksum = Hex.toHexString(doubleHash).substring(0, 8);
        String checkStr = result + checksum;
        String address = Base58.encode(Hex.decode(checkStr));
        walletResponse.setAddress(address);
        return walletResponse;
    }
    /*************************************BCH END*************************************/


    /*************************************ETH START*************************************/
    /**
     * 根据助记词生成ETH地址
     *
     * @param index address_index
     * @return 地址
     */
    public static WalletResponse getEthAddress(List<String> words, Integer index) {
        index = index == null ? 0 : index;
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        String path = "m/44'/60'/0'/0/" + index;
        DeterministicSeed ethDeterministicSeed = new DeterministicSeed(words, null, "", 0L);
        DeterministicKeyChain ethDeterministicKeyChain = DeterministicKeyChain.builder().seed(ethDeterministicSeed).build();
        BigInteger privKey = ethDeterministicKeyChain.getKeyByPath(parsePath(path), true).getPrivKey();
        walletResponse.setPrivateKey(privKey.toString(16));
        ECKeyPair keyPair = ECKeyPair.create(privKey);
        //通过公钥生成钱包地址
        String address = Keys.getAddress(keyPair.getPublicKey());
        walletResponse.setAddress("0x" + address);
        return walletResponse;
    }
    /*************************************ETH END*************************************/


    /*************************************EOS START*************************************/
    /**
     * 根据助记词生成EOS公钥
     *
     * @param index address_index
     * @param words 助记词
     * @return 地址
     */
    public static WalletResponse getEosAddress(List<String> words, Integer index) {
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        index = index == null ? 0 : index;
        String path = "m/44'/194'/0'/0/" + index;
        NetworkParameters networkParameters = MainNetParams.get();
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, getSeedByMnemonic(words), "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey(), false);
        String privateKeyAsWiF = ecKey.getPrivateKeyAsWiF(networkParameters);
        walletResponse.setPrivateKey(privateKeyAsWiF);
        //公钥生成地址
        byte[] pubKey = ECKey.fromPrivate(keyByPath.getPrivKey()).getPubKey();
        RIPEMD160Digest digest = new RIPEMD160Digest();
        digest.update(pubKey, 0, pubKey.length);
        byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
        digest.doFinal(ripemd160Bytes, 0);
        String ripemd160Str = Hex.toHexString(ripemd160Bytes);
        String result = Hex.toHexString(pubKey) + ripemd160Str.substring(0, 8);
        String address = "EOS" + Base58.encode(Hex.decode(result));
        walletResponse.setAddress(address);
        return walletResponse;
    }
    /*************************************EOS END*************************************/


    /************************************TRX START************************************/
    /**
     * 生成波场地址
     *
     * @param index   地址下标
     * @param netType 网络类型 1主网 2测试网
     * @return TRX地址
     */
    public static WalletResponse getTrxAddress(List<String> words, Integer index, Integer netType) {
        WalletResponse response = new WalletResponse();
        response.setWords(words);
        index = index == null ? 0 : index;
        byte[] seedBytes = MnemonicCode.toSeed(words, "");
        //seed生成私钥
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, seedBytes, "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        String path = "m/44'/195'/0'/0/" + index;
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey());
        //生成私钥
        byte[] privKeyBytes = ecKey.getPrivKeyBytes();
        BigInteger privKey = new BigInteger(1, privKeyBytes);
        response.setPrivateKey(Hex.toHexString(privKeyBytes));

        ECPoint point = org.bitcoinj.core.ECKey.CURVE.getG().multiply(privKey);
        byte[] publicKeyBytes = point.getEncoded(false);
        byte[] hash = Hash.sha3(copyOfRange(publicKeyBytes, 1, publicKeyBytes.length));
        byte[] addressBytes = copyOfRange(hash, 11, hash.length);
        if (netType == 1) {
            //1主网
            addressBytes[0] = (byte) 0x41;
        } else if (netType == 2) {
            //2测试网
            addressBytes[0] = (byte) 0x41;
        } else {
            log.error("网络类型参数传入错误 netType={}", netType);
            throw new IllegalArgumentException();
        }
        byte[] hash0 = Sha256Sm3Hash.hash(addressBytes);
        byte[] hash1 = Sha256Sm3Hash.hash(hash0);
        byte[] inputCheck = new byte[addressBytes.length + 4];
        System.arraycopy(addressBytes, 0, inputCheck, 0, addressBytes.length);
        System.arraycopy(hash1, 0, inputCheck, addressBytes.length, 4);
        String address = Base58.encode(inputCheck);
        response.setAddress(address);
        return response;
    }
    /*************************************TRX END*************************************/


    /*************************************XRP START*************************************/
    /**
     * 根据助记词获取XRP地址
     *
     * @param words 助记词
     * @param index address_index
     * @return
     */
    public static WalletResponse getXrpAddress(List<String> words, Integer index) {
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        index = index == null ? 0 : index;
        String path = "m/44'/144'/0'/0/" + index;
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, getSeedByMnemonic(words), "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey());
        String privateKeyAsWiF = ecKey.getPrivateKeyAsHex();
        walletResponse.setPrivateKey(privateKeyAsWiF);
        //公钥生成地址
        //1.SHA256哈希计算
        byte[] hash = Sha256Hash.hash(ecKey.getPubKey());
        //2.RIPEMD160哈希计算
        RIPEMD160Digest digest = new RIPEMD160Digest();
        digest.update(hash, 0, hash.length);
        byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
        digest.doFinal(ripemd160Bytes, 0);
        String accountId = Hex.toHexString(ripemd160Bytes);
        //3.取第2步结果，添加前缀版本号00进行双SHA256哈希计算
        String result = "00" + accountId;
        byte[] firstHash = Sha256Hash.hash(Hex.decode(result));
        byte[] doubleHash = Sha256Hash.hash(firstHash);
        //4.取第3步结果前4字节作为校验值
        String check = Hex.toHexString(doubleHash).substring(0, 8);
        //5.连接有效负载和校验值 进行Base58编码得到XRP地址
        String response = result + check;
        String address = new B58(XRP_DICTIONARY).encodeToString(Hex.decode(response));
        walletResponse.setAddress(address);
        return walletResponse;
    }

    /**
     * 根据密码获取XRP地址
     *
     * @param password 密码 必须保证唯一
     * @return XRP地址
     */
    public static String getXrpAddressByPassword(String password) {
        //这个不执行下面会异常
        Seed test = Seed.fromBase58(TEST_SEED);
        //生成seed 参数一样，生成的seed也会一样 所以必须保证传入的参数是唯一的
        Seed seed = Seed.fromPassPhrase(password);
        //根据seed生成地址
        IKeyPair keyPair = Seed.getKeyPair(seed.toString());
        //1.SHA256 160哈希计算
        byte[] bytes = Utils.sha256hash160(keyPair.canonicalPubBytes());
        String accountId = Hex.toHexString(bytes);
        //2.取上一步结果，添加前缀版本号00进行双SHA256哈希计算
        String result = "00" + accountId;
        byte[] firstHash = Sha256Hash.hash(Hex.decode(result));
        byte[] doubleHash = Sha256Hash.hash(firstHash);
        //3.取上一步结果前4字节作为校验值
        String check = Hex.toHexString(doubleHash).substring(0, 8);
        //4.连接有效负载和校验值 进行Base58编码得到XRP地址
        String response = result + check;
        return new B58(XRP_DICTIONARY).encodeToString(Hex.decode(response));
    }

    /**
     * 返回XRP私钥 seed
     *
     * @param password 密码
     * @return 私钥
     */
    public static String exportXrpPrivateKeyWif(String password) {
        //这个不执行下面会异常
        Seed test = Seed.fromBase58(TEST_SEED);
        //生成seed 参数一样，生成的seed也会一样 所以必须保证传入的参数是唯一的
        Seed seed = Seed.fromPassPhrase(password);
        return seed.toString();
    }
    /*************************************XRP END*************************************/


    /*************************************LTC START*************************************/
    /**
     * 根据助记词生成LTC地址
     *
     * @param words 助记词
     * @param index address_index
     * @return 地址
     */
    public static WalletResponse getLtcAddress(List<String> words, Integer index) {
        WalletResponse walletResponse = new WalletResponse();
        walletResponse.setWords(words);
        index = index == null ? 0 : index;
        String path = "m/44'/2'/0'/0/" + index;
        DeterministicSeed deterministicSeed = new DeterministicSeed(words, getSeedByMnemonic(words), "", 0L);
        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
        DeterministicKey keyByPath = deterministicKeyChain.getKeyByPath(parsePath(path), true);
        ECKey ecKey = ECKey.fromPrivate(keyByPath.getPrivKey());
        //私钥
        String pathPrivateKeyHex = ecKey.getPrivateKeyAsHex();
        String result = "b0" + pathPrivateKeyHex + "01";
        String privateKeyAsWiF = Base58Check.bytesToBase58(Hex.decode(result));
        walletResponse.setPrivateKey(privateKeyAsWiF);
        //公钥生成地址
        String publicKeyHex = ecKey.getPublicKeyAsHex();
        //1.SHA256哈希
        byte[] sha256Bytes = Sha256Hash.hash(Hex.decode(publicKeyHex));
        //2.将第1步结果进行RIPEMD160哈希
        RIPEMD160Digest digest = new RIPEMD160Digest();
        digest.update(sha256Bytes, 0, sha256Bytes.length);
        byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
        digest.doFinal(ripemd160Bytes, 0);
        //3.将LTC地址版本号(30)加在第2步结果签名 LTC测试网对应为6F
        String vResult = "30" + Hex.toHexString(ripemd160Bytes);
        //4.将第3步结果进行双SHA256哈希
        byte[] sha256Hash = Sha256Hash.hashTwice(Hex.decode(vResult));
        //5.取第4步结果的前4字节，并加在第3步结果后面
        String checksum = Hex.toHexString(sha256Hash).substring(0, 8);
        String checkStr = vResult + checksum;
        String address = Base58.encode(Hex.decode(checkStr));
        walletResponse.setAddress(address);
        return walletResponse;
    }

    /*************************************LTC END*************************************/

    private static List<ChildNumber> parsePath(String path) {
        String[] parsedNodes = path.replace("m", "").split("/");
        List<ChildNumber> nodes = new ArrayList();
        String[] var3 = parsedNodes;
        int var4 = parsedNodes.length;
        for (int var5 = 0; var5 < var4; ++var5) {
            String n = var3[var5];
            n = n.replaceAll(" ", "");
            if (n.length() != 0) {
                boolean isHard = n.endsWith("'");
                if (isHard) {
                    n = n.substring(0, n.length() - 1);
                }
                int nodeNumber = Integer.parseInt(n);
                nodes.add(new ChildNumber(nodeNumber, isHard));
            }
        }
        return nodes;
    }

    @Data
    public static class WalletResponse {
        /**
         * 助记词
         */
        private List<String> words;

        /**
         * WIF格式私钥
         */
        private String privateKey;

        /**
         * 地址
         */
        private String address;
    }

    public static void main(String[] args) throws Exception {

        //12个助记词
        List<String> words = new ArrayList<>(12);
        //appear material coach bring occur animal scorpion marble satisfy mom train brick
        //suffer diagram economy kiss own ridge explain empty vivid bar dish town
        words.add("suffer");
        words.add("diagram");
        words.add("economy");
        words.add("kiss");
        words.add("own");
        words.add("ridge");
        words.add("explain");
        words.add("empty");
        words.add("vivid");
        words.add("bar");
        words.add("dish");
        words.add("town");
        System.out.println(getBtcAddress(words, 0, 2).toString());
        /*words.add("address");
        words.add("sunset");
        words.add("machine");
        words.add("limb");
        words.add("offer");
        words.add("drastic");
        words.add("palace");
        words.add("head");
        words.add("ocean");
        words.add("pipe");
        words.add("obey");
        words.add("female");*/

        WalletResponse xrpAddressByMnemonic = getXrpAddress(generateMnemonicWords(), 0);
        System.out.println(xrpAddressByMnemonic.toString());

    }

}
